package intopo.util;

import intopo.evogame.partnerselection.Cache;

import cern.jet.random.engine.MersenneTwister;

import java.awt.Color;
import java.util.Scanner;
import java.io.FileOutputStream;
import java.io.PrintStream;
import java.io.BufferedOutputStream;
import java.io.FileNotFoundException;
import java.util.regex.Pattern;
import javax.swing.UIManager;

/**
 * Provides several utility functions.  These functions are:
 * 
 * <ul>
 * 
 * <li>Calculate a random number in a given interval.</li>
 * 
 * <li> Perturb a number using a normal distribution.  The result may be restricted in a given range.</li>
 * 
 * <li> Read a primitive type from a scanner and report error in case the scanner is empty or does not have the given type.</li>
 * 
 * </ul>
 * @author pedro
 */
public class Util
{
	/**
	 * Flips a coin and return {@code true} with probability {@code flipProbabilityAcceptDivision}.
	 * <p><b>Pre-condition</b> {@code flipProbabilityAcceptDivision} has a value in the range {@code 0} to {@code 1} inclusive.  If the parameter is below the interval, the method always returns {@code false}.
	 * If the parameter is above the interval, the method returns {@code true}.
	 *
	 * @param flipProbabilityAcceptDivision The probability to return {@code true}.
	 *
	 * @return {@code true} with probability {@code flipProbabilityAcceptDivision}.
	 */
	public static boolean flipCoin (float flipProbabilityAcceptDivision)
	{
		return Cache.supply.nextFloat () < flipProbabilityAcceptDivision;
	}
	/**
	 * Return a number between {@code 0} (inclusive) and {@code range} (exclusevly) using a uniform random distribution.
	 * @param range
	 * @return
	 */
	static public int nextPositiveInt (int range)
	{
		return Math.abs (Cache.supply.nextInt () % range);
	}
	static public int perturbNormal (int value, double stddev, int min, int max)
	{
		return Math.max (min, Math.min ((int) Math.round (value + Cache.normal.nextDouble(stddev, 0)), max));
	}
	static public double perturbNormal (double value, double stddev, double min, double max)
	{
		return Math.max (min, Math.min (value + Cache.normal.nextDouble (stddev, 0), max));
	}
	static public float perturbNormal (float value, float stddev, float min, float max)
	{
		return (float) Math.max (min, Math.min (value + Cache.normal.nextDouble (stddev, 0), max));
	}
	static public int perturbNormal (int value, float stddev, int average)
	{
		return (int) Math.round (value + Cache.normal.nextDouble(stddev, average));
	}
	/**
	 * Fill array {@code result} with a random combination of {@code
	 * result.length} elements taken from array {@code work}.  Array {@code
	 * result} is sorted.  Array {@code work} can be used in subsequent
	 * calls to this method.
	 */
	static public void randomCombination (int []result, int []work, MersenneTwister supply)
	{
		int i, max;
		i = 0;
		max = work.length;
		while (i < result.length) {
			int sel, swap;
			sel = Math.abs (supply.nextInt ()) % max;
			result [i] = work [sel];
			max--;
			swap = work [max];
			work [max] = work [sel];
			work [sel] = swap;
			i++;
		}
		java.util.Arrays.sort (result);
	}
	/**
	 * Parse the next float in the given scanner.  After reading the float,
	 * the rest of the line is read.  If the scanner is empty or the next
	 * token is not a float an error is throw and an error message is
	 * printed in the standard error output stream.
	 */
	static public float nextFloatExt (Scanner scanner, String errorMsg)
	{
		if (scanner.hasNextFloat ()) {
			float result = scanner.nextFloat ();
			scanner.nextLine ();
			return result;
		}
		else {
			System.err.print ("Expecting a float value: ");
			System.err.println (errorMsg);
			if (scanner.hasNext ()) {
				System.err.print ("Contains [");
				System.err.print (scanner.next ());
				System.err.println ("]");
			}
			else {
				System.err.println ("Does not contain any token.");
			}
			System.exit (1);
			throw new Error (errorMsg);
		}
	}
	static private Pattern wordPattern = Pattern.compile ("\\S+");
	/**
	 * Parse the next word in the given scanner.  After reading the word,
	 * the rest of the line is read.  If the scanner is empty an error is throw and an error message is
	 * printed in the standard error output stream.
	 */
	static public String nextWordExt (Scanner scanner, String errorMsg)
	{
		if (scanner.hasNext (wordPattern)) {
			String result = scanner.next (wordPattern);
			scanner.nextLine ();
			return result;
		}
		else {
			System.err.print ("Expecting a word value: ");
			System.err.println (errorMsg);
			if (scanner.hasNext ()) {
				System.err.print ("Contains [");
				System.err.print (scanner.next ());
				System.err.println ("]");
			}
			else {
				System.err.println ("Does not contain any token.");
			}
			System.exit (1);
			throw new Error (errorMsg);
		}
	}

	/**
	 * Parse the next integer in the given scanner.  If the scanner is empty or the next token is not an integer an error is throw and an error message is printed in the standard error output stream.
	 */
	static public int nextIntExt (Scanner scanner, String errorMsg)
	{
		if (scanner.hasNextInt ()) {
			int result = scanner.nextInt ();
			scanner.nextLine ();
			return result;
		}
		else {
			System.err.print ("Expecting an int value: ");
			System.err.println (errorMsg);
			if (scanner.hasNext ()) {
				System.err.print ("Contains [");
				System.err.print (scanner.next ());
				System.err.println ("]");
			}
			else {
				System.err.println ("Does not contain any token.");
			}
			System.exit (1);
			throw new Error (errorMsg);
		}
	}
	/**
	 * Opens a text file for writting.  The file is associated with a buffer.
	 */
	static public PrintStream openTextFile (String name)
	{
		FileOutputStream fos;
		BufferedOutputStream bos;
		PrintStream result;
		try {
			fos = new FileOutputStream (name);
		}
		catch (FileNotFoundException e) {
			System.err.println ("Could not open file «" + name + "» for writing");
			System.exit (1);
			fos = null;
		}
		bos = new BufferedOutputStream (fos);
		result = new PrintStream (bos);
		return result;
	}
	/**
	 * Return {@code value} but constrained in the interval {@code min} inclusevely and {@code max} exclusevely.
	 * @param min Lowest value returned
	 * @param value Value to return constrained.
	 * @param max Highest value less one returned.
	 * @return
	 */
	public static int range (int min, int value, int max)
	{
		if (value < min) {
			return min;
		}
		else if (value >= max) {
			return max - 1;
		}
		else {
			return value;
		}
	}
	/**
	 * Updates the colours of the text field component based on the given flag.
	 * @param textField The text field component to update.
	 * @param ok Ok flag.
	 */
	static public void update (final javax.swing.text.JTextComponent textField, final boolean ok)
	{
		if (ok) {
			textField.setForeground (UIManager.getColor ("TextComponent.foreground"));
			textField.setBackground (UIManager.getColor ("TextComponent.background"));
		}
		else {
			textField.setForeground (Color.RED);
			textField.setBackground (Color.WHITE);
		}
	}
}
